# ifndef SALES_DATA_H
# define SALES_DATA_H
#include <iostream>
using std::cerr, std::clog;
using std::cin, std::cout, std::endl;

// 唯一的一点区别是，struct和class的默认访问权限不太一样
// 如果我们使用struct关键字，则定义在第一个访问说明符之前的成员是public的；相反，如果我们使用class关键字，则这些成员是private的。
// 出于统一编程风格的考虑，当我们希望定义的类的所有成员是public的时，使用struct；反之，如果希望成员是private的，使用class。
class Sales_data // 使用class和struct定义类唯一的区别就是默认的访问权限。
{
    // 为Sales_data的非成员函数所做的友元声明
    friend Sales_data add(const std::string &, const std::string &);
    friend std::istream &read(std::istream &, Sales_data &);
    friend std::ostream &print(std::ostream &, const Sales_data &);
    friend std::ostream &operator<<(std::ostream &, const Sales_data &); // 友元函数，重载输出操作符函数，为的是该函数可以访问private成员
    friend std::istream &operator>>(std::istream &, Sales_data &);
    friend bool operator==(const Sales_data &, const Sales_data &);
    friend bool operator!=(const Sales_data &, const Sales_data &);

public: // 添加了访问说明符
    // 新增的构造函数
    Sales_data() = default; // 默认构造函数
    // 和其他函数一样，如果= default在类的内部，则默认构造函数是内联的；如果它在类的外部，则该成员默认情况下不是内联的。
    // 上面的默认构造函数之所以对Sales_data有效，是因为我们为内置类型的数据成员提供了初始值。
    // 如果你的编译器不支持类内初始值，那么你的默认构造函数就应该使用构造函数初始值列表（马上就会介绍）来初始化类的每个成员。
    explicit Sales_data(const std::string &s) : bookNo(s) {} // 构造函数初始值列表指的是，构造函数参数列表冒号后的及函数体左花括号前的那一部分
    Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) {}
    explicit Sales_data(std::istream &);

    // 新成员，关于Sales_data对象的操作
    // 定义在类内部的函数是隐式的inline函数
    std::string isbn() const { return bookNo; } // const成员函数
    // std::string isbn() const { return this->bookNo; } // 同上，但没有必要，因为this是隐式的
    /**
     * 可把isbn常量成员函数想象成如下形式（将this定义为指向常量的常量指针）：
     * std::string Sales_data::isbn(const Sales_data *const this) { return this->bookNo; }
     *
     * 常量对象，以及常量对象的引用或指针都只能调用常量成员函数。
     */
    Sales_data &combine(const Sales_data &);

    Sales_data& operator+=(const Sales_data &rhs); // Sales_data类中复合赋值运算符的定义

private:
    double avg_price() const;

    // 数据成员和2.6.1节相比没有改变
    std::string bookNo;
    unsigned units_sold = 0; // unsigned是unsigned int的缩写
    double revenue = 0.0;
}; // 类必须以分号;结尾

// Sales_data的非成员接口函数
// 友元的声明仅仅指定了访问的权限，而非一个通常意义上的函数声明。
// 如果我们希望类的用户能够调用某个友元函数，那么我们就必须在友元声明之外再专门对函数进行一次声明。
Sales_data add(const Sales_data &, const Sales_data &);
std::ostream &print(std::ostream &, const Sales_data &);
std::istream &read(std::istream &, Sales_data &);
std::ostream &operator<<(std::ostream &, const Sales_data &);
std::istream &operator>>(std::istream &, Sales_data &);
bool operator==(const Sales_data &, const Sales_data &);
bool operator!=(const Sales_data &, const Sales_data &);

// 类外部定义的成员的名字必须包含它所属的类名
double Sales_data::avg_price() const
{
    if (units_sold)
        return revenue / units_sold;
    else
        return 0;
}

// 定义一个返回this对象的函数
Sales_data &Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold; // 把rhs的成员加到this对象的成员上
    revenue += rhs.revenue;
    return *this; // 返回调用该函数的对象，记住this是个常量指针，不要直接返回this，要返回this指向的对象
}

// 定义read和print非成员函数
// 然而当Sales_data类的数据成员被private访问说明符限制后，read非成员函数就无法访问其数据成员了。
std::istream &read(std::istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}

// 定义add非成员函数
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs;
    sum.combine(rhs);
    return sum;
}

// 在类的外部定义构造函数
Sales_data::Sales_data(std::istream &is)
{
    read(is, *this); // 从is中读取一条交易记录然后存入this对象中
}

// 只能在类内声明构造函数时使用explicit关键字，在类外部定义时不应重复。如下是错误的：
// explicit Sales_data::Sales_data(std::istream &is)
// {
//     read(is, *this); // 从is中读取一条交易记录然后存入this对象中
// }

#endif